package agrawala;

import java.io.Serializable;
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Set;
import java.util.logging.Logger;

import myLogger.MyLogger;
import portes.Algo;
import portes.PorteImpl;
import Launcher.Client;
import Launcher.Reso;


public class PorteRicartAgrawala extends UnicastRemoteObject implements Algo, Client{

	private int H;			// estampille locale
	private int HSC;		// estampille de demande d'entrée en section critique
	private boolean R;		// en demande de section critique
	private boolean SC;		// est en section critique
	private Set<Integer> X ;	// ensemble des processus dont l'envoi de REL est differe
	private int nbRel;		// nombre de REL attendus		
	private Set<Integer> V;		// voisinage de la porte

	private static final long serialVersionUID = 8223394217301362731L;
	private int idPorte;		// identifiant de la porte
	private Reso reso;		// reseau pour la communication entre les portes
	private MyLogger log;		// creation de fichier log
	private PorteImpl porte;	// instance de la porte
	private int nbrPlaces;		// nombre de places totales
	private int nbrPortes;		// nombre de portes totales
	private MessageAgrawala message;// message de requetes
	private boolean premiereFoi = true; // variable pour l'inscription au serveur RMI
	Logger logger = Logger.getLogger("LogAgrawala");
	public Queue<MessageAgrawala> queue; // gestion fifo des messages


	/**
	 *	Constructeur de classe
	 * @param porteImpl 
	 * @param nbrPlaces 
	 * @param port 
	 * @param serveur 
	 **/
	public PorteRicartAgrawala(String serveur, int port, int nbrPlaces, PorteImpl porteImpl)  throws RemoteException {
		super();
		this.nbrPlaces = nbrPlaces;
		this.porte = porteImpl;
		X = new HashSet<Integer>();
		V = new HashSet<Integer>();
		
		log = new MyLogger("Ricart_Agrawala_Horloge");
		try {
			reso = (Reso) Naming.lookup("rmi://" + serveur + ":" + port + "/" + Reso.NAME);
		} catch (MalformedURLException e ) {
			log.writeln("Erreur connection objet Reso...\n"+e);
		} catch ( NotBoundException e ) {
			log.writeln("Erreur connection objet Reso...\n"+e);
		}
		log.writeln("Connection Reso etablie...");
		try {
			log.write("Declaration de la porte a RESO...");
			porte.setIdAttribue(reso.declareClient(this));
			log.writeln("Porte declaree. Numero attribue : "+porte.getIdAttribue());
		} catch (RemoteException e) {
			log.writeln("Erreur declaration porte "+e);
		}
		idPorte = porte.getIdAttribue();
		initialisation();
	}

	/**
	 * Initialisation des variables locales
	 */
	public void initialisation(){
		this.H = 0;
		this.HSC = 0;
		this.R = false;
		this.SC = false;
		this.nbRel = 0;
	}

	public void initV(){
		if(premiereFoi){
			try {
				V = reso.getClients();
				V.remove(idPorte);	// suppression de la porte courante de la liste
				nbrPortes = V.size();
				premiereFoi = false;
			} catch (RemoteException e) {
				System.out.println();e.printStackTrace();
			}
		}
		
	}

	/**
	 *	regle 1 : accepte demande de section critique
	 *
	 **/
	public synchronized void demandeSectionCritique(int requete){
		this.R = true;
		this.HSC = this.H + 1;
		this.nbRel = V.size();

		// envoi REQ à tous les voisins
		try{
			Iterator<Integer> itr = V.iterator();
			while(itr.hasNext()){
				Object element = itr.next();
				int destinataire = (Integer) element;
				message = new MessageAgrawala(idPorte, destinataire, Requete.REQ , HSC);
				log.writeln("Envoie message : "+message.toString());
				reso.sendMessage(idPorte, destinataire, message);
			}
			while( nbRel != 0 ){
				this.wait();
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (RemoteException e1) {
			e1.printStackTrace();
		}
		sectionCritique(requete);
	}


	/**
	 *	regle 2 : accepte le message REQ depuis j
	 *
	 **/
	public void receptionReq( int from, int horloge ){
		// si le processus courant est en section critique
		// on stocke le message dans le file d'attente
		//if(SC){
		//queue.add(new MessageAgrawala(from, idPorte, Requete.REQ, horloge));
		//}else{
		this.H = Math.max(HSC, horloge) +1;
		if( R && ((HSC < horloge) || ((HSC == horloge) && idPorte < from))){
			X.add(from);
		}else{
			message = new MessageAgrawala(idPorte, from, Requete.REL, 0);
			try{
				log.writeln("Envoie message : "+message.toString());
				reso.sendMessage(idPorte, from, message);
			}catch( RemoteException e){ e.printStackTrace();}
			//}
		}
	}


	/**
	 *	regle 3 : acceptation message REL
	 *
	 **/
	public void receptionRel(){
		log.write("Reception de REL ");
		this.nbRel = this.nbRel - 1;
		this.notify();
	}


	/**
	 *	regle 4 : accepte liberation SC
	 *
	 **/
	public void libereSectionCritique(){
		this.R = false;
		this.SC = false;
		// on envoie un message REL au premier processus en attente dans le queue

		// envoi du nouveau nombre de places disponibles
		try{
			Iterator<Integer> itr = V.iterator();
			while(itr.hasNext()){
				Object element = itr.next();
				int destinataire = (Integer) element;
				message = new MessageAgrawala(idPorte, destinataire, Requete.PLA , nbrPlaces);
				log.writeln("Envoie message : "+message.toString());
				reso.sendMessage(idPorte, destinataire, message);
			}
		}catch( RemoteException e){ e.printStackTrace();}

		//if(queue.peek() == null){
		try{
			Iterator<Integer> itrX = X.iterator();
			while(itrX.hasNext()){
				Object element = itrX.next();
				int destinataire = (Integer) element;
				message = new MessageAgrawala(idPorte, 0, Requete.REL, 0);
				log.writeln("Envoie message : "+message.toString());
				reso.sendMessage(idPorte, destinataire, message);
			}
		}catch( RemoteException e){ e.printStackTrace();}
		for( int dest : X ){
			//if(){
			try{
				reso.sendMessage(idPorte , dest, message);
			}catch( RemoteException e){ e.printStackTrace();}
		}
		X.removeAll(X);
		log.writeln("Sortie de section critique");
	}


	@Override
	public void demandeEntree() {
		initV();
		log.writeln("On a recu une demande d'entree porte "+idPorte);
		demandeSectionCritique(0);
	}

	@Override
	public void demandeSortie() {
		initV();
		log.writeln("On a recu une demande de sortie porte "+idPorte);
		demandeSectionCritique(1);
	}

	/**
	 *	receiveMessage
	 * @param from
	 * @param to 
	 * @param msg  
	 **/
	@Override
	public synchronized void receiveMessage(int from, int to, Serializable msg) throws RemoteException {
		initV();
		message = (MessageAgrawala) msg;
		log.writeln("Reception message : "+message.toString());
		// reception d'une requete REL
		if( message.requete == Requete.REL ){
			receptionRel();
		}else if(message.requete == Requete.REQ){ // reception d'une requete REQ
			receptionReq(from, message.var);
		} else  // reception d'une mise a jour du nombre de places disponibles
		if( message.requete == Requete.PLA ){
			this.nbrPlaces = message.var;
		}
	}

	/**
	 * Acces à la section critique
	 * @param requete	O si entree, 1 sinon
	 */
	public synchronized void sectionCritique(int requete){
		SC = true;
		if(nbrPlaces > 0){
			// demande d'entree
			if( requete == 0){
				log.writeln("Voiture est ENTREE porte : "+idPorte);
				nbrPlaces--;
			}else{
				log.writeln("Voiture est ENTREE porte : "+idPorte);
				nbrPlaces++;
			}
		}else{
			log.writeln("Pas de place disponible la voiture est REGETEE");
		}
		libereSectionCritique();
	}
}
